package genetic.operators;

import genetic.interfaces.Crossover;
import genetic.interfaces.Individual;
import genetic.interfaces.Mutation;
import genetic.interfaces.Operator;

import java.util.ArrayList;
import java.util.Random;

public class MarioOperator<Fitness extends Comparable<Fitness>, I extends Individual<?, Fitness>> implements Operator<I> {
	
	private ArrayList<Crossover<I>> cross;
	private ArrayList<Mutation<I>>  mut;
	private ArrayList<Integer>      crossWeight;
	private ArrayList<Integer>      mutWeight;

	private double crossProb;
	private double mutProb;
	
	private Random random;
	
	public MarioOperator(double crossProb, double mutProb) {
		this.crossProb = crossProb;
		this.mutProb   = mutProb;
		random      = new Random();
		cross       = new ArrayList<Crossover<I>>();
		mut         = new ArrayList<Mutation<I>>();
		crossWeight = new ArrayList<Integer>();
		mutWeight   = new ArrayList<Integer>();
	}
	
	public void addCrossover(Crossover<I> cross){
		this.cross.add(cross);
		this.crossWeight.add(1);
	}
	
	public void addMutation(Mutation<I> mut){
		this.mut.add(mut);
		this.mutWeight.add(1);
	}

	private int sumCross(){
		int sum = 0;
		for (int i : crossWeight) {
			sum += i;
		}
		return sum;
	}
	
	private int sumMut(){
		int sum = 0;
		for (int i : mutWeight) {
			sum += i;
		}
		return sum;
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public I operation(I ind1, I ind2) {
		I child = (I) ind1.copy();
		boolean crossover = false;
		boolean mutation  = false;
		int choose;
		int weight;
		int sum = 0;
		int crossoverIndex = 0;
		int mutationIndex = 0;
		
		//Escolha do crossover:
		if(random.nextDouble() <= this.crossProb){
			sum = 0;
			crossover = true;
			choose = random.nextInt(sumCross());
			for(crossoverIndex = 0; crossoverIndex < cross.size(); crossoverIndex++){
				sum += crossWeight.get(crossoverIndex);
				if(choose < sum){
					child = cross.get(crossoverIndex).crossover(ind1, ind2);
					break;
				}
			}
		}
		
		//Escolha da mutação:
		if(random.nextDouble() <= this.mutProb){
			sum = 0;
			mutation = true;
			choose = random.nextInt(sumMut());
			for(mutationIndex = 0; mutationIndex < mut.size(); mutationIndex++){
				sum += mutWeight.get(mutationIndex);
				if(choose < sum){
					mut.get(mutationIndex).mutation(child);
					break;
				}
			}
		}
		
		//Aprendizado do operador:
		if(child.eval().compareTo(ind1.eval()) > 0 && child.eval().compareTo(ind2.eval()) > 0){
			if(crossover){
				weight = crossWeight.get(crossoverIndex);
				weight++;
				crossWeight.set(crossoverIndex, weight);
			}
			if(mutation){
				weight = mutWeight.get(mutationIndex);
				weight++;
				mutWeight.set(mutationIndex, weight);
			}
		}else if(child.eval().compareTo(ind1.eval()) < 0 && child.eval().compareTo(ind2.eval()) < 0){
			if(crossover){
				weight = crossWeight.get(crossoverIndex);
				weight = (weight > 1) ? weight - 1 : 1;
				crossWeight.set(crossoverIndex, weight);
			}
			if(mutation){
				weight = mutWeight.get(mutationIndex);
				weight = (weight > 1) ? weight - 1 : 1;
				mutWeight.set(mutationIndex, weight);
			}
		}
		return child;
	}
	
	
	public String debugInfo(){
		String msg = new String();
		msg = "Crossover weights: ";
		for(int i : crossWeight){
			msg += i + " ";
		}
		msg += "\n";
		msg += "Mutation weights: ";
		for(int i : mutWeight){
			msg += i + " ";
		}
		msg += "\n";
		return msg;
	}

}
